 /*******************************************************************************
  * Copyright (c) 2004, 2006 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.core.runtime.dynamichelpers;

 import java.util.HashMap ;
 import java.util.Map ;
 import org.eclipse.core.internal.registry.RegistryMessages;
 import org.eclipse.core.internal.runtime.ReferenceHashSet;
 import org.eclipse.core.internal.runtime.RuntimeLog;
 import org.eclipse.core.runtime.IExtension;
 import org.eclipse.core.runtime.IExtensionDelta;
 import org.eclipse.core.runtime.IExtensionPoint;
 import org.eclipse.core.runtime.IExtensionRegistry;
 import org.eclipse.core.runtime.IRegistryChangeEvent;
 import org.eclipse.core.runtime.IRegistryChangeListener;
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.ListenerList;
 import org.eclipse.core.runtime.RegistryFactory;
 import org.eclipse.core.runtime.Status;

 /**
  * Implementation of the IExtensionTracker.
  * <p>
  * This class can be used without OSGi running.
  * </p>
  * @see org.eclipse.core.runtime.dynamichelpers.IExtensionTracker
  * @since 3.1
  */
 public class ExtensionTracker implements IExtensionTracker, IRegistryChangeListener {
     //Map keeping the association between extensions and a set of objects. Key: IExtension, value: ReferenceHashSet.
 private Map extensionToObjects = new HashMap ();
     private ListenerList handlers = new ListenerList();
     private final Object lock = new Object ();
     private boolean closed = false;
     private IExtensionRegistry registry; // the registry that this tacker works with

     private static final Object [] EMPTY_ARRAY = new Object [0];

     /**
      * Construct a new instance of the extension tracker.
      */
     public ExtensionTracker() {
         this(RegistryFactory.getRegistry());
     }

     /**
      * Construct a new instance of the extension tracker using the given registry
      * containing tracked extensions and extension points.
      *
      * @param theRegistry the extension registry to track
      * @since org.eclipse.equinox.registry 3.2
      */
     public ExtensionTracker(IExtensionRegistry theRegistry) {
         registry = theRegistry;
         if (registry != null)
             registry.addRegistryChangeListener(this);
         else
             RuntimeLog.log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, 0, RegistryMessages.registry_no_default, null));
     }

     /* (non-Javadoc)
      * @see org.eclipse.core.runtime.dynamichelpers.IExtensionTracker#registerHandler(org.eclipse.core.runtime.dynamichelpers.IExtensionChangeHandler, org.eclipse.core.runtime.dynamichelpers.IFilter)
      */
     public void registerHandler(IExtensionChangeHandler handler, IFilter filter) {
         synchronized (lock) {
             if (closed)
                 return;
             // TODO need to store the filter with the handler
 handlers.add(new HandlerWrapper(handler, filter));
         }
     }

     /* (non-Javadoc)
      * @see IExtensionTracker@unregisterHandler(IExtensionChangeHandler)
      */
     public void unregisterHandler(IExtensionChangeHandler handler) {
         synchronized (lock) {
             if (closed)
                 return;
             handlers.remove(new HandlerWrapper(handler, null));
         }
     }

     /* (non-Javadoc)
      * @see IExtensionTracker@registerObject(IExtension, Object, int)
      */
     public void registerObject(IExtension element, Object object, int referenceType) {
         if (element == null || object == null)
             return;

         synchronized (lock) {
             if (closed)
                 return;

             ReferenceHashSet associatedObjects = (ReferenceHashSet) extensionToObjects.get(element);
             if (associatedObjects == null) {
                 associatedObjects = new ReferenceHashSet();
                 extensionToObjects.put(element, associatedObjects);
             }
             associatedObjects.add(object, referenceType);
         }
     }

     /**
      * Implementation of IRegistryChangeListener interface.
      * <p>
      * <em>This method must not be called by clients.</em>
      * </p>
      */
     public void registryChanged(IRegistryChangeEvent event) {
         IExtensionDelta delta[] = event.getExtensionDeltas();
         int len = delta.length;
         for (int i = 0; i < len; i++)
             switch (delta[i].getKind()) {
                 case IExtensionDelta.ADDED :
                     doAdd(delta[i]);
                     break;
                 case IExtensionDelta.REMOVED :
                     doRemove(delta[i]);
                     break;
                 default :
                     break;
             }
     }

     /**
      * Notify all handlers whose filter matches that the given delta occurred.
      * If the list of objects is not <code>null</code> then this is a removal and
      * the handlers will be given a chance to process the list. If it is <code>null</code>
      * then the notification is an addition.
      *
      * @param delta the change to broadcast
      * @param objects the objects to pass to the handlers on removals
      */
     private void notify(IExtensionDelta delta, Object [] objects) {
         // Get a copy of the handlers for safe notification
 Object [] handlersCopy = null;
         synchronized (lock) {
             if (closed)
                 return;

             if (handlers == null || handlers.isEmpty())
                 return;
             handlersCopy = handlers.getListeners();
         }

         for (int i = 0; i < handlersCopy.length; i++) {
             HandlerWrapper wrapper = (HandlerWrapper) handlersCopy[i];
             if (wrapper.filter == null || wrapper.filter.matches(delta.getExtensionPoint())) {
                 if (objects == null)
                     applyAdd(wrapper.handler, delta.getExtension());
                 else
                     applyRemove(wrapper.handler, delta.getExtension(), objects);
             }
         }
     }

     protected void applyAdd(IExtensionChangeHandler handler, IExtension extension) {
         handler.addExtension(this, extension);
     }

     private void doAdd(IExtensionDelta delta) {
         notify(delta, null);
     }

     private void doRemove(IExtensionDelta delta) {
         Object [] removedObjects = null;
         synchronized (lock) {
             if (closed)
                 return;

             ReferenceHashSet associatedObjects = (ReferenceHashSet) extensionToObjects.remove(delta.getExtension());
             if (associatedObjects == null)
                 return;
             //Copy the objects early so we don't hold the lock too long
 removedObjects = associatedObjects.toArray();
         }
         notify(delta, removedObjects);
     }

     protected void applyRemove(IExtensionChangeHandler handler, IExtension removedExtension, Object [] removedObjects) {
         handler.removeExtension(removedExtension, removedObjects);
     }

     /* (non-Javadoc)
      * @see IExtensionTracker@getObjects(IExtension)
      */
     public Object [] getObjects(IExtension element) {
         synchronized (lock) {
             if (closed)
                 return EMPTY_ARRAY;
             ReferenceHashSet objectSet = (ReferenceHashSet) extensionToObjects.get(element);
             if (objectSet == null)
                 return EMPTY_ARRAY;

             return objectSet.toArray();
         }
     }

     /* (non-Javadoc)
      * @see IExtensionTracker@close()
      */
     public void close() {
         synchronized (lock) {
             if (closed)
                 return;
             if (registry != null)
                 registry.removeRegistryChangeListener(this);
             extensionToObjects = null;
             handlers = null;

             closed = true;
         }
     }

     /* (non-Javadoc)
      * @see IExtensionTracker@unregisterObject(IExtension, Object)
      */
     public void unregisterObject(IExtension extension, Object object) {
         synchronized (lock) {
             if (closed)
                 return;
             ReferenceHashSet associatedObjects = (ReferenceHashSet) extensionToObjects.get(extension);
             if (associatedObjects != null)
                 associatedObjects.remove(object);
         }
     }

     /* (non-Javadoc)
      * @see IExtensionTracker@unregisterObject(IExtension)
      */
     public Object [] unregisterObject(IExtension extension) {
         synchronized (lock) {
             if (closed)
                 return EMPTY_ARRAY;
             ReferenceHashSet associatedObjects = (ReferenceHashSet) extensionToObjects.remove(extension);
             if (associatedObjects == null)
                 return EMPTY_ARRAY;
             return associatedObjects.toArray();
         }
     }

     /**
      * Return an instance of filter matching all changes for the given extension point.
      *
      * @param xpt the extension point
      * @return a filter
      */
     public static IFilter createExtensionPointFilter(final IExtensionPoint xpt) {
         return new IFilter() {
             public boolean matches(IExtensionPoint target) {
                 return xpt.equals(target);
             }
         };
     }

     /**
      * Return an instance of filter matching all changes for the given extension points.
      *
      * @param xpts the extension points used to filter
      * @return a filter
      */
     public static IFilter createExtensionPointFilter(final IExtensionPoint[] xpts) {
         return new IFilter() {
             public boolean matches(IExtensionPoint target) {
                 for (int i = 0; i < xpts.length; i++)
                     if (xpts[i].equals(target))
                         return true;
                 return false;
             }
         };
     }

     /**
      * Return an instance of filter matching all changes from a given plugin.
      *
      * @param id the plugin id
      * @return a filter
      */
     public static IFilter createNamespaceFilter(final String id) {
         return new IFilter() {
             public boolean matches(IExtensionPoint target) {
                 return id.equals(target.getNamespaceIdentifier());
             }
         };
     }

     private class HandlerWrapper {
         IExtensionChangeHandler handler;
         IFilter filter;

         public HandlerWrapper(IExtensionChangeHandler handler, IFilter filter) {
             this.handler = handler;
             this.filter = filter;
         }

         public boolean equals(Object target) {
             return handler.equals(((HandlerWrapper) target).handler);
         }

         public int hashCode() {
             return handler.hashCode();
         }
     }

 }

